#pragma once
#include "NodeService.h"
#include "mp2_types.h"
#include "mp2_constants.h"
#include "NodeService_client.h"
#include <protocol/TBinaryProtocol.h>
#include <server/TSimpleServer.h>
#include <transport/TServerSocket.h>
#include <transport/TBufferTransports.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <utility>
#include <iostream>
#include <map>
#include "sha1.h"
#include "functions.h"

using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

using boost::shared_ptr;

using namespace mp2;

class NodeServiceHandler : virtual public NodeServiceIf
{
public:

	//our m value passed in
	int bits;
	//2^m
	int chord_size;
	//our finger table pointers
	NodeInfo * finger_table;
	//the info for this node
	NodeInfo thisnode;
	//our neighbors
	NodeInfo next, prev;
	//the introducer
	NodeInfo introducer;
	//Our neighbor lock
	pthread_mutex_t n_lock;
	//Our file lock
	pthread_mutex_t f_lock;
	//Our keys->data map
	std::map<int32_t, std::string> files;
	//Our filename->keys map
	std::map<std::string, int32_t> file_keys;
	//Says whether we have a valid successor or not
	bool next_set;
	//Says whether we have a valid predecessor or not
	bool prev_set;

	NodeServiceHandler()
	{
		pthread_mutex_init(&n_lock, NULL);
		pthread_mutex_init(&f_lock, NULL);
		next_set = false;
		prev_set = false;
	}

	/**
	 * First function called in initialization.
	 * Sets up this node ID and port. And if this
	 * is the introducer, the neighbors too.
	 * @param info Object containing this node's information
	 */
	void setNodeInfo(const NodeInfo& info)
	{
		thisnode.port = info.port;
		thisnode.id = info.id;
		if (thisnode.id == 0)
		{
			setPredecessor(thisnode);
			setSuccessor(thisnode);
			printf("node= %i: initial successor= %i\n", thisnode.id, thisnode.id);
		}
	}

	/**
	 * Second function called in initialization.
	 * Gives us the info of the introducer, in case
	 * we need it at some point in the future.
	 * @param info Object containing introducer information.
	 */
	void setIntroducer(const NodeInfo& info)
	{
		introducer = info;
	}

	/**
	 * Called immediately after the introducer adds
	 * this node to the system. If initializes our finger
	 * table to the correct values. For the introducer,
	 * it simply sets every pointer to itself.
	 * @param bits Number of bits in the chord system.
	 */
	void setChordInfo(const int32_t bits)
	{
		//Setup and allocate our finger table
		this->bits = bits;
		chord_size = (int) pow((double) 2, (double) bits);
		finger_table = new NodeInfo[bits];
		//If this is the introducer, point them to ourselves
		if (thisnode.id == 0)
		{
			for (int i = 0; i < bits; ++i)
			{
				finger_table[i] = thisnode;
			}
		}
		//Otherwise, calculate the  values
		else
		{
			for (int i = 0; i < bits; ++i)
			{
				int id = (int) (thisnode.id + (int) pow((double) 2, (double) i));
				id %= (int) pow((double) 2, (double) bits);
				this->getFingerPointer(finger_table[i], id);
			}
		}
	}

	/**
	 * Sets the predecessor of this node.
	 * @param info New predecessor information
	 */
	void setPredecessor(const NodeInfo& info)
	{
		pthread_mutex_lock(&n_lock);
		prev = info;
		pthread_mutex_unlock(&n_lock);
		if(!prev_set && prev.id != thisnode.id)
		{
			printf("node= %i: updated predecessor= %i\n", thisnode.id, info.id);
		}
	}

	/**
	 * Sets the successor of this node.
	 * @param info New successor information
	 */
	void setSuccessor(const NodeInfo& info)
	{
		//if this is our first time setting the succesor, print that out
		if(!next_set && info.id != thisnode.id)
		{
			printf("node= %i: initial successor= %i\n", thisnode.id, info.id);
			next_set = true;
		}
		if(info.id != next.id)
		{
			printf("node= %i: updated finger entry: i= 0, pointer= %i\n", thisnode.id, info.id);
		}
		//update our successor and first finger pointer
		pthread_mutex_lock(&n_lock);
		next = info;
		if (finger_table != NULL)
		{
			finger_table[0] = info;
		}
		pthread_mutex_unlock(&n_lock);
		//Alert our new successor of our presence
		if (next.id != thisnode.id)
		{
			NodeClient * client = new NodeClient(next);
			client->client->alertOfNewNode(thisnode);
			delete client;
		}
		askNeighborsForFiles();
	}

	/**
	 * Gets this node's current predecessor
	 * @param _return Current predecessor
	 */
	void getPredecessor(NodeInfo& _return)
	{
		_return = prev;
	}

	/**
	 * Gets this node's current successor
	 * @param _return Current successor
	 */
	void getSuccessor(NodeInfo& _return)
	{
		_return = next;
	}

	/**
	 * Given a calculated point on the ring, this
	 * will give the node that pointer should point
	 * to. Use when calculating finger pointers.
	 * @param _return Node after given id
	 * @param id Calculated id to search with
	 */
	void getFingerPointer(NodeInfo& _return, const int32_t id)
	{
		//If the pointer points to our successor
		if ((id > thisnode.id && id <= next.id) || (id > thisnode.id && next.id == 0))
		{
			_return = next;
		}
		else
		{
			NodeClient * client = new NodeClient(next);
			client->client->getFingerPointer(_return, id);
			//if its trying to point back to ourselves
			//point it to our successor
			if (_return.id == thisnode.id)
			{
				_return = next;
			}
			delete client;
		}
		//Make sure we got a valid node
		//otherwise, just make it this node
		if (_return.port <= 0)
		{
			//printf("got a null finger pointer\n");
			_return = thisnode;
		}
		//printf("Node %i fetched for pointer %i\n", _return.id, id);
	}

	/**
	 * When a new node joins the system, this will
	 * calculate its new successor.
	 * @param _return Node successor
	 * @param id ID of the node
	 */
	void getNewNodeSuccessor(NodeInfo& _return, const int32_t id)
	{
		//see if it belongs right after this node
		if ((id > thisnode.id && id <= next.id) || (id > thisnode.id && next.id == 0))
		{
			_return = next;
		} //otherwise, ask the next node in line to find out
		else
		{
			NodeInfo info = next;
			NodeClient * client = new NodeClient(info);
			client->client->getNewNodeSuccessor(_return, id);
			delete client;
		}
		//Make sure we got a valid node
		//otherwise, just make it this node
		if (_return.port <= 0)
		{
			_return = thisnode;
		}
	}

	/**
	 * Gets the SHA-1 hash of a string
	 * @param filename String to hash
	 * @return Computed hash
	 */
	int32_t getHash(const std::string& filename)
	{
		//Set up our SHA functions
		const char* file = &filename[0];
		SHA1Context sha1;
		SHA1Reset(&sha1);
		//calculate the hash
		SHA1Input(&sha1, (unsigned char*) file, strlen(file));
		//If we couldn't calculate, return -1
		if (!SHA1Result(&sha1))
		{
			return -1;
		}
		//otherwise return the integer hash
		else
		{
			double p = pow((double) 2.0, (double) bits);
			return sha1.Message_Digest[4] % (int) p;
		}
	}

	/**
	 * Gets a file in the system.
	 * @param _return File contents
	 * @param filename File name to retrieve
	 */
	void getFile(GetFileReturn& _return, const std::string& filename)
	{
		//Get the location where the file should be
		NodeInfo loc;
		getFileLocation(loc, filename);
		std::map<std::string, int32_t>::iterator temp;
		temp = file_keys.find(filename);
		//If it belongs to us, or we just happen to have it.
		if(loc.id == thisnode.id || temp != file_keys.end())
		{
			//calculate our hash
			std::map<int32_t, std::string>::iterator it;
			int32_t hash = getHash(filename);
			pthread_mutex_lock(&f_lock);
			//use this to signify a file doesn't exist
			_return.data = ";;dne;;";
			//iterate through our files
			for (it = files.begin(); it != files.end(); ++it)
			{
				//if we find the key, assign the data
				if (it->first == hash)
				{
					_return.data = it->second;
					break;
				}
			}
			//if we never found the file, indicate that
			if (_return.data == ";;dne;;")
			{
				_return.output = get_GET_FILE_result_as_string(filename.c_str(), hash, false, thisnode.id, NULL);
				printf("node= %i: no such file k= %i to serve\n", thisnode.id, hash);
			}
			//otherwise say that we did find it and return the data
			else
			{
				_return.output = get_GET_FILE_result_as_string(filename.c_str(), hash, true, thisnode.id, _return.data.c_str());
				printf("node= %i: served file: k= %i\n", thisnode.id, hash);
			}
			pthread_mutex_unlock(&f_lock);
		}
		//otherwise ask the node that should have it
		else
		{
			NodeClient * client = new NodeClient(loc);
			client->client->getFile(_return, filename);
		}
	}

	/**
	 * Adds a file to the system
	 * @param data Contents of new file
	 * @param filename Name of new file
	 * @return The key of the file added
	 */
	void addFile(std::string& _return, const std::string& data, const std::string& filename)
	{
		NodeInfo loc;
		getFileLocation(loc, filename);
		if(loc.id == thisnode.id)
		{
			int hash = getHash(filename);
			pthread_mutex_lock(&f_lock);
			//Insert our key,data pair in the content map
			files.insert(std::pair<int32_t, std::string > (hash, data));
			//Insert our filename,key pair in our keys map
			file_keys.insert(std::pair<std::string, int32_t > (filename, hash));
			pthread_mutex_unlock(&f_lock);
			//get the formatted output for the listener
			_return = get_ADD_FILE_result_as_string(filename.c_str(), hash, thisnode.id);
			printf("node= %i: added file: k= %i\n", thisnode.id, hash);
		}
		else
		{
			NodeClient * client = new NodeClient(loc);
			client->client->addFile(_return, data, filename);
			delete client;
		}
	}

	/**
	 * Removes a file from the system
	 * @param filename Name of file to remove
	 */
	void deleteFile(std::string& _return, const std::string& filename)
	{
		NodeInfo loc;
		getFileLocation(loc, filename);
		std::map<std::string, int32_t>::iterator temp;
		temp = file_keys.find(filename);
		//If it belongs to us, or we just happen to have it.
		if(loc.id == thisnode.id || temp != file_keys.end())
		{
			std::map<int32_t, std::string>::iterator it;
			std::map<std::string, int32_t>::iterator it2;
			//Get the filename hash
			int hash = getHash(filename);
			pthread_mutex_lock(&f_lock);
			//Find the file in our maps
			it = files.find(hash);
			it2 = file_keys.find(filename);
			//If it doesn't exist, generate return
			if (it == files.end())
			{
				_return = get_DEL_FILE_result_as_string(filename.c_str(), hash, false, thisnode.id);
				printf("node= %i: no such file k= %i to delete\n", thisnode.id, hash);
			}
			else
			{
				files.erase(it);
				file_keys.erase(it2);
				_return = get_DEL_FILE_result_as_string(filename.c_str(), hash, true, thisnode.id);
				printf("node= %i: deleted file: k= %i\n", thisnode.id, hash);
			}
			pthread_mutex_unlock(&f_lock);
		}
		//otherwise, ask the node that should have it to delete it
		else
		{
			NodeClient * client = new NodeClient(loc);
			client->client->deleteFile(_return, filename);
			delete client;
		}
	}

	/**
	 * Gets the node at which a file is stored
	 * @param _return Object of node which has file
	 * @param filename Name of file
	 */
	void getFileLocation(NodeInfo& _return, const std::string& filename)
	{
		if(next.id == thisnode.id && thisnode.id == 0)
		{
			_return = thisnode;
			return;
		}
		if(next.id == thisnode.id)
		{
			NodeClient * client = new NodeClient(introducer);
			client->client->getFileLocation(_return, filename);
			delete client;
		}
		else
		{
			int32_t hash = getHash(filename);
			if(hash > thisnode.id && (hash <= next.id || next.id == 0))
			{
				_return = next;
			}
			else
			{
				NodeClient * client = new NodeClient(next);
				client->client->getFileLocation(_return, filename);
				delete client;
			}
		}
	}

	/**
	 * Adds a node to the chord system. This is
	 * ONLY called by the introducer.
	 * @param info Information of node to add
	 */
	void addNode(const NodeInfo& info)
	{
		if (thisnode.id != 0)
		{
			return;
		}
		//If this is the only node (introducer)
		if (next.id == 0)
		{
			setSuccessor(info);
			setPredecessor(info);
			NodeClient * client;
			client = new NodeClient(info);
			client->client->setPredecessor(thisnode);
			client->client->setSuccessor(thisnode);
			//Now that its in the ring, set the chord info up
			client->client->setChordInfo(bits);
			delete client;
		}
		else
		{
			NodeInfo ret;
			NodeClient * client;
			//Get the new node's successor
			getNewNodeSuccessor(ret, info.id);
			//Connect to the node, and let it
			//know if its new successor
			client = new NodeClient(info);
			client->client->setSuccessor(ret);
			client->client->setChordInfo(bits);
			delete client;
		}
	}

	/**
	 * This function is called when a node sets THIS
	 * node as its successor. This node will then decide
	 * whether that node is a better predecessor than
	 * the current one, and make changes accordingly.
	 * @param info Node that set this as successor
	 */
	void alertOfNewNode(const NodeInfo& info)
	{
		//If our predecessor is already the one handed to us, return
		if (info.id == prev.id)
		{
			return;
		}
		//Otherise, set that as the predecessor if it's a better fit.
		if (info.id > prev.id || prev.id == thisnode.id)
		{
			setPredecessor(info);
		}
	}

	/**
	 * Retrieves a node from the system.
	 * @param _return The node
	 * @param id ID of node to retrieve
	 */
	void getNode(NodeInfo& _return, const int32_t id)
	{
		//If they want this node, return it
		if (id == thisnode.id)
		{
			_return = thisnode;
		}
		//otherwise ask our successor
		else
		{
			NodeClient * client = new NodeClient(next);
			client->client->getNode(_return, id);
			delete client;
		}
	}

	/**
	 * Returns a formatted output string of finger table and keys table.
	 * @param _return Output string.
	 * @param info Node to get table for.
	 */
	void getTable(std::string& _return, const NodeInfo & info)
	{
		//If they want a different node, ask it
		if (info.id != thisnode.id)
		{
			NodeClient * client = new NodeClient(info);
			client->client->getTable(_return, info);
			delete client;
		}
		//otherwise return our data
		else
		{
			std::vector<NodeInfo> fingerTable;
			std::map<int32_t, FileMap> keysTable;
			getFingerTable(fingerTable, info);
			getKeys(keysTable, info);
			_return = get_GET_TABLE_result_as_string(fingerTable, bits, info.id, 0, keysTable);
		}
	}

	/**
	 * Gets the finger table of a node in the
	 * form of an int->int map. entry->node form
	 * @param _return Map of finger table
	 * @param info The node to get the finger table for
	 */
	void getFingerTable(std::vector<NodeInfo>& _return, const NodeInfo& info)
	{
		_return.clear();
		for (int i = 0; i < bits; ++i)
		{
			_return.push_back(finger_table[i]);
		}
	}

	/**
	 * Get the keys of every file stored at a node
	 * @param _return The vector of keys
	 * @param info The node to get the list for
	 */
	void getKeys(std::map<int32_t, FileMap> & _return, const NodeInfo& info)
	{
		_return.clear();
		std::map<std::string, int32_t>::iterator it;
		//iterate through our keys table
		for(it=file_keys.begin();it!=file_keys.end();++it)
		{
			FileMap m;
			m.name = it->first;
			m.data = files[it->second];
			//add file info to return value
			_return.insert(std::pair<int32_t, FileMap>(it->second, m));
		}
	}

	/**
	 * Called on the introducer, which will print its
	 * ID, then pass it around the ring and have every
	 * node print its ID in the order of the ring.
	 */
	void listNodes(const int32_t n)
	{
		printf("The %i node in line is node: %i\n", n, thisnode.id);
		if (next.id != 0)
		{
			NodeClient * client = new NodeClient(next);
			int nn = n + 1;
			client->client->listNodes(nn);
			delete client;
		}
	}

	/**
	 * Called by our fix thread on the fixInterval.
	 * Chooses a random entry in the table to update.
	 */
	void updateFingerTable()
	{
		if (thisnode.id == next.id)
		{
			//nothing for now
		}
		else
		{
			//Get the index to update
			int index = (rand() % (bits - 1)) + 1;
			//Calculate the finger pointer
			int id = (int) (thisnode.id + (int) pow((double) 2, (double) index));
			id %= (int) (pow((double) 2, (double) bits));
			//Get the node after that pointer
			int old_pointer = finger_table[index].id;
			this->getFingerPointer(finger_table[index], id);
			if (old_pointer != finger_table[index].id)
			{
				printf("node= %i: updated finger entry: i= 0, pointer= %i\n", thisnode.id, finger_table[index].id);
			}
		}
	}

	/**
	 * Called by our stabilization thread on the
	 * stabilize interval. If asks this node's
	 * successor for its predecessor, and updates
	 * accordingly.
	 */
	void stabilize()
	{
		if (thisnode.id == next.id)
		{
			//nothing for now
		}
		else
		{
			NodeClient * client;
			//Connect to successor
			client = new NodeClient(next);
			//ask for its predecessor
			NodeInfo pred;
			client->client->getPredecessor(pred);
			delete client;
			//set that as our new successor
			if (pred.id != next.id && pred.id != thisnode.id)
			{
				setSuccessor(pred);
			}
		}
	}

	void askNeighborsForFiles()
	{
		std::map<int32_t, FileMap> tFiles;
		std::map<int32_t, FileMap>::iterator it;
		std::string foo;
		tFiles.clear();
		//If we're not pointing to ourselves
		if (next.id != thisnode.id && next.id >= 0)
		{
			//connect to our successor and get his files
			NodeClient * client = new NodeClient(next);
			client->client->getKeys(tFiles, next);
			//iterate through his files
			for (it = tFiles.begin(); it != tFiles.end(); ++it)
			{
				//if one of those files should belong to us, move it
				if(it->first <= thisnode.id)
				{
					//tell them to delete the file
					client->client->deleteFile(foo, it->second.name);
					//add it to our files manually. DO NOT call addFile()
					pthread_mutex_lock(&f_lock);
					file_keys.insert(std::pair<std::string, int32_t>(it->second.name,it->first));
					files.insert(std::pair<int32_t, std::string>(it->first, it->second.data));
					pthread_mutex_unlock(&f_lock);
					printf("node= %i: added file: k= %i\n", thisnode.id, it->first);
				}
			}
			delete client;
		}
	}
};

